//
//  jvmrun.m
//  iCodeMacRun
//
//  Created by Andrew Barry on 30/05/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "jvmrun.h"

static jvmrun *gjvm;
static NSString *gJDBCDriver = @"postgresql-8.3-604.jdbc3";
static NSString *gJDBCClass = @"org.postgresql.Driver";
static NSString *gJDBCConnection = @"jdbc:postgresql://10.0.1.13/postgres";

@implementation jvmrun
+(jvmrun *) instance {
	if (!gjvm) {
		gjvm = [jvmrun new];
		[gjvm startupJava];
	}
	return gjvm;
}

+(void) setJDBCDriver: (NSString *) driver {
	gJDBCDriver = [driver retain];
}

+(void) setJDBCClass: (NSString *) className {
	gJDBCClass = [className retain];
}

+(void) setJDBCConnection: (NSString *) connStr {
	gJDBCConnection = [connStr retain];
}

-(void)startupJava {
    int result = 0;
    JavaVM* theVM;

    /* default vm args */
    JavaVMInitArgs  vm_args;
	JavaVMOption options[1];

	NSString *dblayerPath = [[NSBundle mainBundle] pathForResource:@"dblayer" ofType:@"jar"];	
	NSString *optionString = [NSString stringWithFormat:@"-Djava.class.path=%@", dblayerPath];
	if (gJDBCDriver) {
		NSString *dbdriverPath = [[NSBundle mainBundle] pathForResource:[gJDBCDriver stringByDeletingPathExtension] ofType:@"jar"];
		optionString = [optionString stringByAppendingFormat:@":%@", dbdriverPath];
	}
	
	options[0].optionString = [optionString UTF8String];
	
    vm_args.version  = JNI_VERSION_1_4;
	JNI_GetDefaultJavaVMInitArgs(&vm_args);
    vm_args.options  = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized  = JNI_TRUE;
	
    result = JNI_CreateJavaVM(&theVM, (void**)&env, &vm_args);
	
	if (gJDBCClass) {
		 (*env)->FindClass(env, [[gJDBCClass stringByReplacingOccurrencesOfString:@"." withString:@"/"] UTF8String]);
	}
	
	doubleClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/lang/Double"));
	doubleConstructor = (*env)->GetMethodID(env, doubleClass, "<init>", "(D)V");
	integerClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/lang/Integer"));
	integerConstructor = (*env)->GetMethodID(env, integerClass, "<init>", "(I)V");
	
	dblayerClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "bcs/icode/db/DBConnection"));
	dblayerConstructor = (*env)->GetMethodID(env, dblayerClass, "<init>", "()V");
		
	dbconnect = (*env)->GetMethodID(env, dblayerClass, "connect", "()Z");
	dbsetconn = (*env)->GetMethodID(env, dblayerClass, "setConnection", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
	dbclose = (*env)->GetMethodID(env, dblayerClass, "close", "()V");
	dbselect = (*env)->GetMethodID(env, dblayerClass, "select", "(Ljava/lang/String;[Ljava/lang/Object;)Lbcs/icode/db/DBResultSet;");
	dbexecute = (*env)->GetMethodID(env, dblayerClass, "exec", "(Ljava/lang/String;[Ljava/lang/Object;)Z");
}

-(bool) dbConnect: (NSString *)  uid withPassword: (NSString *) pwd {
	if (dbconnection)
		[self dbClose];
		
	dbconnection = (*env)->NewGlobalRef(env, (*env)->NewObject(env, dblayerClass, dblayerConstructor));
	
	(*env)->CallVoidMethod(env, dbconnection, dbsetconn, (*env)->NewStringUTF(env, [gJDBCConnection UTF8String]), (*env)->NewStringUTF(env, [uid UTF8String]), (*env)->NewStringUTF(env, [pwd UTF8String]));
	
	if ((*env)->CallBooleanMethod(env, dbconnection, dbconnect))
		return true;
	[self dbClose];
	return false;
}

-(void) dbClose {
	if (dbconnection) {
		(*env)->CallVoidMethod(env, dbconnection, dbclose);
		(*env)->DeleteGlobalRef(env, dbconnection);
		dbconnection = nil;
		dbconnectionRef = nil;
	}
}

-(jarray) buildArgArray: (jsval *) args withArgCount: (int) argCount {
	jclass objectClass = (*env)->FindClass(env, "java/lang/Object");
	jarray sqlparams = (*env)->NewObjectArray(env, argCount, objectClass, nil);
	
	for (int idx = 0; idx < argCount; idx++) {
		if (JSVAL_IS_NULL(args[idx]) || JSVAL_IS_VOID(args[idx])) {
		} else if (JSVAL_IS_INT(args[idx])) {
			int nval = JSVAL_TO_INT(args[idx]);
			jobject boxedInt = (*env)->NewObject(env, integerClass, integerConstructor, nval);
			
			(*env)->SetObjectArrayElement(env, sqlparams, idx, boxedInt);
		} else if (JSVAL_IS_NUMBER(args[idx])) {
			double v;
			JS_ValueToNumber(cx, args[idx], &v);
			
			jobject boxedDouble = (*env)->NewObject(env, doubleClass, doubleConstructor, v);
			
			(*env)->SetObjectArrayElement(env, sqlparams, idx, boxedDouble);
		} else {
			NSString *str = valToString(cx, args[idx]);
			(*env)->SetObjectArrayElement(env, sqlparams, idx, (*env)->NewStringUTF(env, [str UTF8String]));
		}
	}
	return sqlparams;
}

-(bool) dbExecute: (const char *) sql withArgs: (jsval *) args withArgCount: (int) argCount {
	if (!dbconnection) {
		if (![self dbConnect:@"" withPassword:@""])
			return false;
	}

	jarray sqlparams = [self buildArgArray:args withArgCount:argCount];
	
	return (*env)->CallBooleanMethod(env, dbconnection, dbexecute, (*env)->NewStringUTF(env, sql), sqlparams);
}

-(jsval) dbSelect: (const char *) sql withArgs: (jsval *) args withArgCount: (int) argCount {
	if (!dbconnection) {
		if (![self dbConnect:@"" withPassword:@""])
			return false;
	}
	jclass stringClass = (*env)->FindClass(env, "java/lang/String");
	jclass numberClass = (*env)->FindClass(env, "java/lang/Number");
	jclass sqlDateClass = (*env)->FindClass(env, "java/sql/Date");
	jclass dbResultSetClass = (*env)->FindClass(env, "bcs/icode/db/DBResultSet");
	
	jfieldID selectFieldID = (*env)->GetFieldID(env, dbResultSetClass, "rows", "[[Ljava/lang/Object;");
	jfieldID selectColumnsID = (*env)->GetFieldID(env, dbResultSetClass, "columns", "[Ljava/lang/String;");

	jclass javaDateClass = (*env)->FindClass(env, "java/util/Date");
	jmethodID numberDoubleMethod = (*env)->GetMethodID(env, numberClass, "doubleValue", "()D");
	jmethodID getTimeMethod = (*env)->GetMethodID(env, javaDateClass, "getTime", "()J");

	jarray sqlparams = [self buildArgArray:args withArgCount:argCount];
	
	jobject selectResult = (*env)->CallObjectMethod(env, dbconnection, dbselect, (*env)->NewStringUTF(env, sql), sqlparams);
	if (selectResult == nil)
		return JSVAL_NULL;
	
	jarray result = (*env)->GetObjectField(env, selectResult, selectFieldID);
	jarray columns =(*env)->GetObjectField(env, selectResult, selectColumnsID); 
	
	JSObject *names = nil;
	JSObject *arr = nil;
	JSObject *row = nil;
	JS_AddRoot(cx, &arr);
	JS_AddRoot(cx, &row);
	JS_AddRoot(cx, &names);
	
	arr = JS_NewArrayObject(cx, 0, nil);
	names = JS_NewArrayObject(cx, 0, nil);
	
	int colCount = (*env)->GetArrayLength(env, columns);
	for (int idx = 0; idx < colCount; idx++) {
		jstring colName = (*env)->GetObjectArrayElement(env, columns, idx);
		const char *str = (*env)->GetStringUTFChars(env, colName, nil);
		jsval jsName = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
		JS_SetElement(cx, names, idx, &jsName);
	}

	int len = (*env)->GetArrayLength(env, result);
	for (int idx = 0; idx < len; idx++) {
		jarray arrItem = (*env)->GetObjectArrayElement(env, result, idx);
		row = JS_NewArrayObject(cx, 0, nil);
		for (int colIdx = 0; colIdx < colCount; colIdx++) {
			jobject colItem = (*env)->GetObjectArrayElement(env, arrItem, colIdx);
			jsval v = JSVAL_NULL;
			if (colItem == nil)
				v = JSVAL_NULL;
			else if ((*env)->IsInstanceOf(env, colItem, stringClass)) {
				const char *str = (*env)->GetStringUTFChars(env, colItem, nil);
				v = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
				(*env)->ReleaseStringUTFChars(env, colItem, str);
			} else if ((*env)->IsInstanceOf(env, colItem, numberClass)) {
				double dv = (*env)->CallDoubleMethod(env, colItem, numberDoubleMethod);
				JS_NewNumberValue(cx, dv, &v);
			} else if ((*env)->IsInstanceOf(env, colItem, javaDateClass)) {
				long long dv = (*env)->CallLongMethod(env, colItem, getTimeMethod);
				jsval timev;
				JS_NewNumberValue(cx, dv, &timev);
				JS_CallFunctionName(cx, global, "__constructDate", 1, &timev, &v);
			} else
				v = JSVAL_NULL;
			JS_SetElement(cx, row, colIdx, &v);
		}
		jsval rowv = OBJECT_TO_JSVAL(row);
		JS_SetElement(cx, arr, idx, &rowv);
	}
	
	jsval vrows = OBJECT_TO_JSVAL(arr);
	jsval vnames = OBJECT_TO_JSVAL(names);
	
	JSObject *dbresult = JS_NewObject(cx, nil, nil, nil);
	JS_SetProperty(cx, dbresult, "rows", &vrows);
	JS_SetProperty(cx, dbresult, "columns", &vnames);
	
	JS_RemoveRoot(cx, &row);
	JS_RemoveRoot(cx, &arr);
	JS_RemoveRoot(cx, &names);
	return OBJECT_TO_JSVAL(dbresult);
}


@end
